package com.w3cnet.leetcode.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;

public class RsaUtil {
    /**
     * 加密算法
     */
    public static final String ALGORITHM = "RSA";

    /**
     * 填充方式
     */
    public static final String PADDING_FORMAT = "RSA/ECB/PKCS1Padding";

    /**
     * 明文块最大值
     */
    private static final int MAX_ENCRYPT_BLOCK = 245;

    /**
     * 密文块最大值
     */
    private static final int MAX_DECRYPT_BLOCK = 256;

    /**
     * 加密
     * @param text  明文
     * @param publicKey  公钥
     * @param charset 字符编码
     * @return  加密后的字符串（base64）
     * @throws Exception
     */
    public static String encrypt(String text, String publicKey, String charset)
            throws Exception {

        byte[] data = text.getBytes(charset);
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);

        // 对数据加密
        Cipher cipher = Cipher.getInstance(PADDING_FORMAT);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;

        // 对数据分段加密
        while (length - offSet > 0) {
            int len = length - offSet > MAX_ENCRYPT_BLOCK ? MAX_ENCRYPT_BLOCK : length - offSet;
            cache = cipher.doFinal(data, offSet, len);
            out.write(cache, 0, cache.length);

            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }

        byte[] cipherBytes = out.toByteArray();
        out.close();

        return Base64.encodeBase64String(cipherBytes);
    }

    /**
     * 私钥解密
     * @param ciphertext  密文（base64）
     * @param privateKey  私钥
     * @param charset  字符编码
     * @return
     * @throws Exception
     */
    public static String decrypt(String ciphertext, String privateKey, String charset)
            throws Exception {

        byte[] data = Base64.decodeBase64(ciphertext);
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);

        Cipher cipher = Cipher.getInstance(PADDING_FORMAT);
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;

        while (length - offSet > 0) {
            int len = length - offSet > MAX_DECRYPT_BLOCK ? MAX_DECRYPT_BLOCK : length - offSet;
            cache = cipher.doFinal(data, offSet, len);
            out.write(cache, 0, cache.length);

            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }

        byte[] textBytes = out.toByteArray();
        out.close();

        return new String(textBytes, charset);
    }

    /**
     * 从文件中加载密钥字符串
     * @return 是否成功
     * @throws Exception
     */
    public static String loadKeyString(String keyFile){
        String keyString="";
        InputStream in=null;
        BufferedReader br=null;
        try {
            in= RsaUtil.class.getResourceAsStream("/"+keyFile);
            br= new BufferedReader(new InputStreamReader(in));
            String readLine= null;
            StringBuilder sb= new StringBuilder();
            while((readLine= br.readLine())!=null){
                if(readLine.charAt(0)=='-'){
                    continue;
                }else{
                    sb.append(readLine);
                    sb.append('\r');
                }
            }
            keyString=sb.toString();
        } catch (IOException e) {
        } catch (Exception e) {

        }finally{
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }
        return keyString;
    }

    /**
     * 从文件中加载密钥字符串  根据文件路径加载
     * @return 是否成功
     * @throws Exception
     */
    public static String loadKeyStringByPath(String keyFile){
        String keyString="";
        InputStream in=null;
        BufferedReader br=null;
        try {
            in = new FileInputStream(keyFile);
            br= new BufferedReader(new InputStreamReader(in));
            String readLine= null;
            StringBuilder sb= new StringBuilder();
            while((readLine= br.readLine())!=null){
                if(readLine.charAt(0)=='-'){
                    continue;
                }else{
                    sb.append(readLine);
                    sb.append('\r');
                }
            }
            keyString=sb.toString();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } catch (Exception e) {
            System.out.println(e.getMessage());

        }finally{
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }
        return keyString;
    }

//    public static void main(String[] args) throws Exception {
//        String publicKey = RSAUtils.loadKeyStringByPath("D:/sso_public_key_test.pem");
//        System.out.println(RSACryptoUtil.encryptByPublicKey("123", publicKey, "utf-8"));
//    }


    /**
     * 随机生成密钥对
     * @throws NoSuchAlgorithmException
     */
    public static Map<Integer, String> genKeyPair() throws NoSuchAlgorithmException {
        Map<Integer, String> keyMap = new HashMap<Integer, String>();  //用于封装随机产生的公钥与私

        // KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 初始化密钥对生成器，密钥大小为96-1024位
        keyPairGen.initialize(1024,new SecureRandom());
        // 生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        // 得到私钥字符串
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        // 将公钥和私钥保存到Map
        keyMap.put(0,publicKeyString);  //0表示公钥
        keyMap.put(1,privateKeyString);  //1表示私钥

        return keyMap;
    }

}